fix(mocks): omit 'struct' constraint when type parameter has 'unmanaged' constraint - #6473
Conversation
…ed' constraint Roslyn reports HasValueTypeConstraint for 'unmanaged'-constrained type parameters, so the mock generator emitted 'where T : struct, unmanaged', which is invalid C# (CS0449) and cascades into CS8377 across the generated files. Emit only 'unmanaged', which already implies a non-nullable value type. Fixes #6471
Greptile SummaryThis PR fixes a bug where
Confidence Score: 5/5Safe to merge — the change is a targeted one-line guard that eliminates invalid generated code without affecting any other constraint path. The fix is minimal and precisely scoped, targeting only the struct emission branch without touching any other constraint path. The existing IsUnconstrained helper already handles HasUnmanagedTypeConstraint independently, keeping the change internally consistent. Two new tests (snapshot + compile-guard) and an updated pre-existing snapshot directly verify the fix eliminates the invalid constraint combination. No files require special attention. Important Files Changed
Flowchart%%{init: {'theme': 'neutral'}}%%
flowchart TD
A[ITypeParameterSymbol] --> B{HasReferenceTypeConstraint?}
B -- Yes --> C[emit 'class']
B -- No --> D{HasValueTypeConstraint?}
C --> D
D -- No --> E{HasUnmanagedTypeConstraint?}
D -- Yes --> F{HasUnmanagedTypeConstraint?}
F -- No --> G[emit 'struct']
F -- Yes --> H["skip 'struct' fix #6471"]
G --> E
H --> E
E -- Yes --> I[emit 'unmanaged']
E -- No --> J{HasNotNullConstraint?}
I --> J
J -- Yes --> K[emit 'notnull']
J -- No --> L[emit ConstraintTypes]
K --> L
L --> M{HasConstructorConstraint?}
M -- Yes --> N["emit 'new()'"]
M -- No --> O["join with comma"]
N --> O
Reviews (1): Last reviewed commit: "fix(mocks): omit 'struct' constraint whe..." | Re-trigger Greptile |
Code reviewThe fix itself is correct, minimal, and well-tested: is the right fix, and the new snapshot + compile-guard tests (plus the corrected pre-existing snapshot) directly cover it. Suggestion: the same bug pattern is duplicated — and still live — elsewhere
A related but distinct issue: Why this matters beyond "another instance to patch": the constraint-string-building logic ( This is out of scope for this specific PR (which is correctly scoped to the reported issue in the mocks generator), so not a blocker — but worth a fast follow-up before someone hits CS0449 again via |
|
Follow-up for the review comment above (sibling generators with the same |
…6474) Follow-up to #6473 (review feedback): the same Roslyn quirk — 'unmanaged' also sets HasValueTypeConstraint — was latent in sibling generators: - CollectionShapeAssertionGenerator: emitted 'where T : struct, unmanaged' (CS0449) for forwarded methods with an 'unmanaged' type parameter. - PropertyInjectionSourceGenerator: 'struct' branch of the else-if chain shadowed the 'unmanaged' branch, silently downgrading the constraint. - AssertionExtensionGenerator, AssertionMethodGenerator, MethodAssertionGenerator, ShouldExtensionGenerator: never emitted 'unmanaged' at all, downgrading it to 'struct' (CS8377 when the generated code forwards to the unmanaged-constrained original). All sites now emit 'struct' only when the type parameter is not 'unmanaged'-constrained, and emit 'unmanaged' where it was missing. Refs #6471
Updated [TUnit](https://github.com/thomhurst/TUnit) from 1.61.15 to 1.61.38. <details> <summary>Release notes</summary> _Sourced from [TUnit's releases](https://github.com/thomhurst/TUnit/releases)._ ## 1.61.38 <!-- Release notes generated using configuration in .github/release.yml at v1.61.38 --> ## What's Changed ### Other Changes * fix: apply 'unmanaged' constraint handling to remaining source generators by @thomhurst in thomhurst/TUnit#6474 * feat: expose data source attributes on TestContext by @thomhurst in thomhurst/TUnit#6477 ### Dependencies * chore(deps): update tunit to 1.61.35 by @thomhurst in thomhurst/TUnit#6475 **Full Changelog**: thomhurst/TUnit@v1.61.35...v1.61.38 ## 1.61.35 <!-- Release notes generated using configuration in .github/release.yml at v1.61.35 --> ## What's Changed ### Other Changes * fix(mocks): omit 'struct' constraint when type parameter has 'unmanaged' constraint by @thomhurst in thomhurst/TUnit#6473 ### Dependencies * chore(deps): bump fast-uri from 3.1.2 to 3.1.4 in /docs by @dependabot[bot] in thomhurst/TUnit#6466 * chore(deps): update tunit to 1.61.29 by @thomhurst in thomhurst/TUnit#6467 * chore(deps): update dependency verify.tool to v0.8.0 by @thomhurst in thomhurst/TUnit#6470 * chore(deps): update dependency docusaurus-plugin-llms to ^0.5.1 by @thomhurst in thomhurst/TUnit#6472 **Full Changelog**: thomhurst/TUnit@v1.61.29...v1.61.35 ## 1.61.29 <!-- Release notes generated using configuration in .github/release.yml at v1.61.29 --> ## What's Changed ### Other Changes * perf: remove EnumerableAsyncProcessor dependency by @thomhurst in thomhurst/TUnit#6465 ### Dependencies * chore(deps): bump shell-quote from 1.8.4 to 1.10.0 in /docs by @dependabot[bot] in thomhurst/TUnit#6462 * chore(deps): bump body-parser from 1.20.5 to 1.20.6 in /docs by @dependabot[bot] in thomhurst/TUnit#6461 * chore(deps): bump webpack-dev-server from 5.2.5 to 5.2.6 in /docs by @dependabot[bot] in thomhurst/TUnit#6460 * chore(deps): update dependency microsoft.kiota.abstractions to v2 by @thomhurst in thomhurst/TUnit#6464 * chore(deps): update tunit to 1.61.23 by @thomhurst in thomhurst/TUnit#6463 **Full Changelog**: thomhurst/TUnit@v1.61.23...v1.61.29 ## 1.61.23 <!-- Release notes generated using configuration in .github/release.yml at v1.61.23 --> ## What's Changed ### Other Changes * fix(mocks): emit 'where T : default' for interface-constrained generic methods by @thomhurst in thomhurst/TUnit#6458 ### Dependencies * chore(deps): update tunit to 1.61.15 by @thomhurst in thomhurst/TUnit#6450 * chore(deps): update actions/checkout action to v7.0.1 by @thomhurst in thomhurst/TUnit#6453 * chore(deps): update verify to 31.26.0 by @thomhurst in thomhurst/TUnit#6454 * chore(deps): update verify to 31.27.0 by @thomhurst in thomhurst/TUnit#6457 * chore(deps): update react to ^19.2.8 by @thomhurst in thomhurst/TUnit#6459 **Full Changelog**: thomhurst/TUnit@v1.61.15...v1.61.23 Commits viewable in [compare view](thomhurst/TUnit@v1.61.15...v1.61.38). </details> [](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores) Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting `@dependabot rebase`. [//]: # (dependabot-automerge-start) [//]: # (dependabot-automerge-end) --- <details> <summary>Dependabot commands and options</summary> <br /> You can trigger Dependabot actions by commenting on this PR: - `@dependabot rebase` will rebase this PR - `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it - `@dependabot show <dependency name> ignore conditions` will show all of the ignore conditions of the specified dependency - `@dependabot ignore this major version` will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this minor version` will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this dependency` will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself) </details> Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Updated [TUnit.Core](https://github.com/thomhurst/TUnit) from 1.61.15 to 1.61.38. <details> <summary>Release notes</summary> _Sourced from [TUnit.Core's releases](https://github.com/thomhurst/TUnit/releases)._ ## 1.61.38 <!-- Release notes generated using configuration in .github/release.yml at v1.61.38 --> ## What's Changed ### Other Changes * fix: apply 'unmanaged' constraint handling to remaining source generators by @thomhurst in thomhurst/TUnit#6474 * feat: expose data source attributes on TestContext by @thomhurst in thomhurst/TUnit#6477 ### Dependencies * chore(deps): update tunit to 1.61.35 by @thomhurst in thomhurst/TUnit#6475 **Full Changelog**: thomhurst/TUnit@v1.61.35...v1.61.38 ## 1.61.35 <!-- Release notes generated using configuration in .github/release.yml at v1.61.35 --> ## What's Changed ### Other Changes * fix(mocks): omit 'struct' constraint when type parameter has 'unmanaged' constraint by @thomhurst in thomhurst/TUnit#6473 ### Dependencies * chore(deps): bump fast-uri from 3.1.2 to 3.1.4 in /docs by @dependabot[bot] in thomhurst/TUnit#6466 * chore(deps): update tunit to 1.61.29 by @thomhurst in thomhurst/TUnit#6467 * chore(deps): update dependency verify.tool to v0.8.0 by @thomhurst in thomhurst/TUnit#6470 * chore(deps): update dependency docusaurus-plugin-llms to ^0.5.1 by @thomhurst in thomhurst/TUnit#6472 **Full Changelog**: thomhurst/TUnit@v1.61.29...v1.61.35 ## 1.61.29 <!-- Release notes generated using configuration in .github/release.yml at v1.61.29 --> ## What's Changed ### Other Changes * perf: remove EnumerableAsyncProcessor dependency by @thomhurst in thomhurst/TUnit#6465 ### Dependencies * chore(deps): bump shell-quote from 1.8.4 to 1.10.0 in /docs by @dependabot[bot] in thomhurst/TUnit#6462 * chore(deps): bump body-parser from 1.20.5 to 1.20.6 in /docs by @dependabot[bot] in thomhurst/TUnit#6461 * chore(deps): bump webpack-dev-server from 5.2.5 to 5.2.6 in /docs by @dependabot[bot] in thomhurst/TUnit#6460 * chore(deps): update dependency microsoft.kiota.abstractions to v2 by @thomhurst in thomhurst/TUnit#6464 * chore(deps): update tunit to 1.61.23 by @thomhurst in thomhurst/TUnit#6463 **Full Changelog**: thomhurst/TUnit@v1.61.23...v1.61.29 ## 1.61.23 <!-- Release notes generated using configuration in .github/release.yml at v1.61.23 --> ## What's Changed ### Other Changes * fix(mocks): emit 'where T : default' for interface-constrained generic methods by @thomhurst in thomhurst/TUnit#6458 ### Dependencies * chore(deps): update tunit to 1.61.15 by @thomhurst in thomhurst/TUnit#6450 * chore(deps): update actions/checkout action to v7.0.1 by @thomhurst in thomhurst/TUnit#6453 * chore(deps): update verify to 31.26.0 by @thomhurst in thomhurst/TUnit#6454 * chore(deps): update verify to 31.27.0 by @thomhurst in thomhurst/TUnit#6457 * chore(deps): update react to ^19.2.8 by @thomhurst in thomhurst/TUnit#6459 **Full Changelog**: thomhurst/TUnit@v1.61.15...v1.61.23 Commits viewable in [compare view](thomhurst/TUnit@v1.61.15...v1.61.38). </details> [](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores) Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting `@dependabot rebase`. [//]: # (dependabot-automerge-start) [//]: # (dependabot-automerge-end) --- <details> <summary>Dependabot commands and options</summary> <br /> You can trigger Dependabot actions by commenting on this PR: - `@dependabot rebase` will rebase this PR - `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it - `@dependabot show <dependency name> ignore conditions` will show all of the ignore conditions of the specified dependency - `@dependabot ignore this major version` will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this minor version` will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this dependency` will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself) </details> Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Summary
Fixes #6471.
Mock.Of<T>()over a generic interface whose type parameter has anunmanagedconstraint generated code that does not compile. Roslyn reportsHasValueTypeConstraint == trueforunmanaged-constrained type parameters, soGetGenericConstraintsemittedwhere T : struct, unmanaged— invalid C# (CS0449), cascading into ~30 CS8377 errors across the generated mock, impl, factory, and extension files.Fix
In
MethodSymbolExtensions.GetGenericConstraints(the single constraint-emission helper, used for both interface-level and method-level type parameters), only emitstructwhen the type parameter does not also have theunmanagedconstraint.unmanagedalready implies a non-nullable value type.Tests
Interface_With_Unmanaged_Constrained_Type_Parametercoveringwhere T : unmanaged(with the issue'sref Trepro shape) andwhere T : unmanaged, IDisposable.Interface_With_Unmanaged_Constrained_Type_Parameter_Compilesasserting the generated output produces no CS0449/CS8377.Interface_With_Generic_Method_Constraints_On_Explicit_Impl.verified.txt: the pre-existingGetUnmanaged<T>()case now emitswhere T : unmanagedinstead ofwhere T : struct, unmanaged(only change in the snapshot).All 80 TUnit.Mocks.SourceGenerator.Tests and 1160 TUnit.Mocks.Tests pass locally (net10.0).